package com.ecloud.common.data.dynamic;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.sql.DataSource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import com.ecloud.common.data.annotation.DynamicDS;
import com.ecloud.common.data.handler.DSExpressionHandler;
import com.ecloud.common.data.handler.DSHeaderHandler;
import com.ecloud.common.data.handler.DSParameterHandler;
import com.ecloud.common.data.handler.DSSessionHandler;

/**
 * 动态数据源
 *
 * @author LiuGangQiang Create in 2021/04/27
 */
@Aspect
@Order(-1)
public abstract class DynamicDataSource extends AbstractRoutingDataSource implements IDynamicDataSource {
    private final static Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
    /**
     * 动态数据源处理器
     *
     * @author LiuGangQiang Create in 2021/07/14
     */
    private DSHeaderHandler handler;

    /**
     * 主数据源名
     *
     * @author LiuGangQiang Create in 2021/04/27
     */
    public final static String MASTER = "master";
    /**
     * 数据源集合
     *
     * @author LiuGangQiang Create in 2021/04/29
     */
    private static ConcurrentHashMap<Object, Object> dataSources = new ConcurrentHashMap<>();

    /**
     * 构造器
     *
     * @author LiuGangQiang Create in 2021/05/01
     */
    public DynamicDataSource() {
        /* 设置解析器责任链 处理优先级:header > session > parameter > expression(SpEL) */
        handler = new DSHeaderHandler();
        DSSessionHandler sessionHandler = new DSSessionHandler();
        DSParameterHandler parameterHandler = new DSParameterHandler();
        DSExpressionHandler expressionHandler = new DSExpressionHandler();
        handler.setNext(sessionHandler);
        sessionHandler.setNext(parameterHandler);
        parameterHandler.setNext(expressionHandler);
    }

    /**
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getDataSource();
    }

    /**
     * @see com.ecloud.common.data.aspect.DynamicDSAspect#delDataSource(java.lang.String)
     */
    @Override
    public boolean delDataSource(String key) {
        if (DataSourceContext.containDataSourceKey(key)) {
            DataSourceContext.delDataSourceKey(key);
            DynamicDataSource.dataSources.remove(key);
            /* 重新设置数据源 */
            super.setTargetDataSources(DynamicDataSource.dataSources);
            /* 这行代码很重要 执行它才会刷新数据源 */
            super.afterPropertiesSet();
            return true;
        }
        return false;
    }

    /**
     * @see com.ecloud.common.data.aspect.DynamicDSAspect#addDataSource(java.lang.String)
     */
    @Override
    public synchronized boolean addDataSource(String key) {
        if (!DataSourceContext.containDataSourceKey(key)) {
            DataSource dataSource = registerDataSource(key);
            if (dataSource == null) {
                return false;
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("dynamic datasource register target database --> [{}]", key);
            }
            DataSourceContext.addDataSourceKey(key);
            DynamicDataSource.dataSources.put(key, dataSource);
            /* 重新设置数据源 */
            super.setTargetDataSources(DynamicDataSource.dataSources);
            /* 这行代码很重要 执行它才会刷新数据源 */
            super.afterPropertiesSet();
        }
        return true;
    }

    /**
     * @see com.ecloud.common.data.aspect.DynamicDSAspect#setDataSources(java.util.Map)
     */
    @Override
    public void setDataSources(Map<Object, Object> dataSources) {
        DynamicDataSource.dataSources.putAll(dataSources);
        DataSourceContext.addDataSourceKeys(dataSources.keySet());
        super.setTargetDataSources(dataSources);
        /* 这行代码很重要 执行它才会刷新数据源 */
        super.afterPropertiesSet();
    }

    /**
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#getConnection()
     */
    @Override
    public Connection getConnection() throws SQLException {
        /* 这里需要重写获取连接的实现方式,需要从当前数据源中获取连接,而不是主数据源,这里这样写主要是因为要实现多数据源事务控制需要 */
        return super.getResolvedDataSources().get(DataSourceContext.getDataSource()).getConnection();
    }

    /**
     * 切换数据源
     *
     * @author LiuGangQiang Create in 2021/04/28
     * @param point 切面
     * @param ds    注解
     */
    @Before("@annotation(ds) || @within(ds)")
    private void switchDataSource(JoinPoint point, DynamicDS ds) {
        /* 获取方法签名 */
        MethodSignature signature = (MethodSignature) point.getSignature();
        /* 先获取方法级注解 */
        DynamicDS mds = signature.getMethod().getAnnotation(DynamicDS.class);
        if (mds != null) {
            /* 方法级不为空则设置为方法级注解 */
            ds = mds;
        }
        /* 获取SpEL表达式 */
        String spel = null;
        if (ds != null && !StringUtils.isBlank(spel = ds.value())) {
            String value = handler.resolve(point, spel);
            /* 判断是否包含该数据源 */
            if (!DataSourceContext.containDataSourceKey(value)) {
                /* 不包含则调用方法添加数据源 */
                if (addDataSource(value)) {
                    /* 添加成功切换数据源 */
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("dynamic datasource changed  target database --> [{}]", value);
                    }
                    DataSourceContext.switchDataSource(value);
                }
            } else {
                /* 包含数据源则直接切换数据源 */
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("dynamic datasource changed  target database --> [{}]", value);
                }
                DataSourceContext.switchDataSource(value);
            }
        }
    }

    /**
     * 清除数据源
     *
     * @author LiuGangQiang Create in 2021/04/28
     * @param point 切面
     * @param ds    注解
     */
    @After("@annotation(ds) || @within(ds)")
    private void clearDataSource(JoinPoint point, DynamicDS ds) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("dynamic datasource cleared  target database --> [{}]", DataSourceContext.getDataSource());
        }
        /* 清除数据源设置 */
        DataSourceContext.clearDataSource();
    }
}
